#include "game_server.h"


//-----------------------


typedef struct
{
    float x, y;
    float w, h;
    int xtile, ytile;
} _Rect;

typedef struct
{
    float x, y;
    float r;
} _Circle;

typedef struct
{
    vec2 p1;
    vec2 p2;
} _Segment;

_Rect _game_rect_from_entity(Game_Entity *e);
_Circle _game_circle_from_entity(Game_Entity *e);
_Rect _game_rect_from_tilemap(GFX_Tilemap *t, int x, int y);
int _game_rects_from_tilemap(GFX_Tilemap *t, _Rect area, _Rect *rects, int maxRects);

int _game_collide_rect(_Rect r1, _Rect r2);
int _game_collide_circle(_Circle c1, _Circle c2);
void _game_collide_vec_response(float ticks, float x1, float y1, float x2, float y2, float *nx1, float *ny1, float *nx2, float *ny2);
int _game_collide_segment_circle(_Segment rect, Game_Entity *e, vec2 new_pos);
void _game_collide_tilemap_response(_Rect rect, Game_Entity *e);

_Rect _game_rect_from_entity(Game_Entity *e)
{
    _Rect r;
    r.x = e->x - e->radius/2.;
    r.y = e->y - e->radius/2.;
    r.w = r.h = e->radius;
    return r;
}

_Circle _game_circle_from_entity(Game_Entity *e)
{
    _Circle c;
    c.x = e->x;
    c.y = e->y;
    c.r = e->radius;
    return c;
}

_Rect _game_rect_from_tilemap(GFX_Tilemap *t, int x, int y)
{
    _Rect r;
    r.w = t->image->width / t->image->columns;
    r.h = t->image->height / t->image->rows;
    r.x = t->x + x*r.w;
    r.y = t->y + y*r.h;
    return r;
}

int _game_rects_from_tilemap(GFX_Tilemap *t, _Rect area, _Rect *rects, int maxRects)
{
    area.x -= t->x;
    area.y -= t->y;
    // Get tilemap collison box
    int mapx, mapy, mapw, maph;
    mapx = (area.x / (t->image->width / t->image->columns));
    mapy = (area.y / (t->image->height / t->image->rows));
    mapw = (area.w / (t->image->width / t->image->columns))+2;
    maph = (area.h / (t->image->height / t->image->rows))+2;
    // Clamp box
    mapx = i_clamp(mapx, 0, t->width - 1);
    mapy = i_clamp(mapy, 0, t->height - 1);
    mapw = i_clamp(mapw, 0, t->width - mapx);
    maph = i_clamp(maph, 0, t->height - mapy);

    // Produce rects
    int nbRects = 0;
    for(int y = mapy; y < mapy+maph; y++)
    {
        for(int x = mapx; x < mapx+mapw; x++)
        {
            if(nbRects > maxRects) return maxRects;

            rects[nbRects] = _game_rect_from_tilemap(t, x, y);
            rects[nbRects].xtile = x;
            rects[nbRects].ytile = y;
            nbRects++;
        }
    }
    return nbRects;
}

int _game_collide_rect(_Rect r1, _Rect r2)
{
    if(r1.x + r1.w < r2.x) return 0;
    if(r1.y + r1.h < r2.y) return 0;

    if(r1.x > r2.x + r2.w) return 0;
    if(r1.y > r2.y + r2.h) return 0;

    return 1;
}

int _game_collide_circle(_Circle c1, _Circle c2)
{
    float dx = c1.x - c2.x;
    float dy = c1.y - c2.y;
    return dx*dx + dy*dy < (c1.r+c2.r)*(c1.r+c2.r);
}

void _game_collide_vec_response(float ticks, float x1, float y1, float x2, float y2, float *nx1, float *ny1, float *nx2, float *ny2)
{
    float dx, dy;
    dx = x2 - x1;
    dy = y2 - y1;
    ticks *= 2;
    if(nx1) *nx1 -= dx * ticks;
    if(ny1) *ny1 -= dy * ticks;
    if(nx2) *nx2 += dx * ticks;
    if(ny2) *ny2 += dy * ticks;
}

int _game_collide_segment_circle(_Segment seg, Game_Entity *e, vec2 new_pos)
{
    vec2 p1;
    p1[0] = seg.p1[0];
    p1[1] = seg.p1[1];
    vec2 p2;
    p2[0] = seg.p2[0];
    p2[1] = seg.p2[1];
    vec2 ePos = {e->x, e->y};

    vec2 seg_v, unit_seg_v, dist_v, normal_v;
    vec2 pt_v, proj_v, closest_to_seg;

    float proj_v_length;

    // Compute seg_v and pt_v
    vec2_sub(seg_v, p2, p1);
    vec2_sub(pt_v, ePos, p1);

    // Project circle center (pt_v) onto seg_v
    vec2_mult(unit_seg_v, seg_v, 1./vec2_length(seg_v));
    proj_v_length = vec2_dot(pt_v, unit_seg_v);
    vec2_mult(proj_v, unit_seg_v, proj_v_length);

    proj_v[0] += p1[0];
    proj_v[1] += p1[1];

    // Force closest_to seg between p1 and p2
    if(proj_v_length < 0)
    {
        closest_to_seg[0] = p1[0];
        closest_to_seg[1] = p1[1];
    }
    else if(proj_v_length > vec2_length(seg_v))
    {
        closest_to_seg[0] = p2[0];
        closest_to_seg[1] = p2[1];
    }
    else
    {
        closest_to_seg[0] = proj_v[0];
        closest_to_seg[1] = proj_v[1];
    }

    vec2_sub(dist_v, ePos, closest_to_seg);

    // Test if the entity collide with the segment
    if(vec2_length(dist_v) > e->radius/2)
    {
        return 0;
    }

    // Calculate new position
    vec2_mult(dist_v, dist_v, e->radius/2 * 1./vec2_length(dist_v));
    new_pos[0] = closest_to_seg[0] + dist_v[0];
    new_pos[1] = closest_to_seg[1] + dist_v[1];

    // Calculate normal
    normal_v[0] = p2[1] - p1[1];
    normal_v[1] = - (p2[0] - p1[0]);
    vec2_mult(normal_v, normal_v, 1./vec2_length(normal_v));

    return 1;
}

void _game_collide_tilemap_response(_Rect r, Game_Entity *e)
{
    _Segment seg;
    vec2 newPos;
    // Top
    seg.p1[0] = r.x;
    seg.p1[1] = r.y;
    seg.p2[0] = seg.p1[0] + r.w;
    seg.p2[1] = seg.p1[1] + 0;
    if(_game_collide_segment_circle(seg, e, newPos))
    {
        e->x = newPos[0];
        e->y = newPos[1];
        //return;
    }
    // Left
    seg.p1[0] = r.x;
    seg.p1[1] = r.y;
    seg.p2[0] = seg.p1[0];
    seg.p2[1] = seg.p1[1] + r.h;
    if(_game_collide_segment_circle(seg, e, newPos))
    {
        e->x = newPos[0];
        e->y = newPos[1];
        //return;
    }
    // Bottom
    seg.p1[0] = r.x;
    seg.p1[1] = r.y + r.h;
    seg.p2[0] = seg.p1[0] +  r.w;
    seg.p2[1] = seg.p1[1];
    if(_game_collide_segment_circle(seg, e, newPos))
    {
        e->x = newPos[0];
        e->y = newPos[1];
        //return;
    }
    // Right
    seg.p1[0] = r.x + r.w;
    seg.p1[1] = r.y;
    seg.p2[0] = seg.p1[0];
    seg.p2[1] = seg.p1[1] + r.h;
    if(_game_collide_segment_circle(seg, e, newPos))
    {
        e->x = newPos[0];
        e->y = newPos[1];
        //return;
    }
}


//-----------------------


Game* game_server_spawn(void)
{
    Game *game = mem_allocate(sizeof(Game));
    game->time = clock_new();
    game->players = array_new(MAX_PLAYERS, sizeof(Game_Player));
    game->entities = array_new(MAX_ENTITIES, sizeof(Game_Entity));
    game->localPlayers = array_new(0, sizeof(int));
    return game;
}

void game_server_start(Game *server)
{
    clock_start(server->time);
    server->lastTime = clock_get(server->time);
}

void game_server_pause(Game *server)
{
    clock_pause(server->time);
}

int game_server_ticks(Game *server)
{
    float dT = clock_get(server->time) - server->lastTime;
    server->lastTime = clock_get(server->time);
    for(int i = 0; i < array_size(server->entities); i++)
    {
        Game_Entity *entity = &server->entities[i];
        if(entity->deleteFlag)
        {
            entity->inUse = 0;
            memset(&entity->sprite, 0, sizeof(GFX_Sprite));
        }
        else if(entity->inUse)
        {
            entity->x += entity->dx * dT;
            entity->y += entity->dy * dT;
        }
    }

    // General logic
    if(server->logic)
    {
        if((*server->logic)(server, dT))
        {
            return -1;
        }
    }
    else
    {
        return -1;
    }

    // Inter-entity logic
    if(server->collide_entity)
    {
        for(int i = 0; i < array_size(server->entities); i++)
        {
            Game_Entity *e1 = &server->entities[i];
            if(e1->inUse)
            {
                for(int j = 0; j < array_size(server->entities); j++)
                {
                    Game_Entity *e2 = &server->entities[j];
                    if(e2->inUse && i != j)
                    {
                        _Circle c1 = _game_circle_from_entity(e1);
                        _Circle c2 = _game_circle_from_entity(e2);

                        if(_game_collide_circle(c1, c2))
                        {
                            if(e1->entityType <= e2->entityType)
                            {
                                (*server->collide_entity)(server, dT, e1, e2);
                            }
                            else
                            {
                                (*server->collide_entity)(server, dT, e2, e1);
                            }
                        }
                    }
                }
            }
        }
    }

    // Entity-map logic
    if(server->collide_tilemap)
    {
        for(int i = 0; i < array_size(server->entities); i++)
        {
            Game_Entity *e = &server->entities[i];
            if(e->inUse)
            {
                _Rect collideMapRects[16];
                _Rect entityRect = _game_rect_from_entity(e);
                int nbRects = _game_rects_from_tilemap(server->tilemap, entityRect, collideMapRects, 16);

                for(int i = 0; i < nbRects; i++)
                {
                    if(_game_collide_rect(entityRect, collideMapRects[i]))
                    {
                        (*server->collide_tilemap)(server, dT, e, collideMapRects[i].xtile, collideMapRects[i].ytile);
                    }
                }
            }
        }
    }
    return 0;
}

Game_Entity* game_entity_spawn(Game *server)
{
    for(int i = 0; i < array_size(server->entities); i++)
    {
        Game_Entity *entity = &server->entities[i];
        if(entity->inUse == 0)
        {
            entity->inUse = 1;
            entity->deleteFlag = 0;
            entity->entityIndex = i;
            return entity;
        }
    }
    log_puts(LOG_ERR, "Ran out of entities!");
    exit(EXIT_FAILURE);
}

Game_Player* game_player_spawn(Game *server)
{
    for(int i = 0; i < array_size(server->players); i++)
    {
        Game_Player *player = &server->players[i];
        if(player->inUse == 0)
        {
            memset(player, 0, sizeof(Game_Player));
            player->inUse = 1;
            player->playerIndex = i;
            Game_Entity *entity = game_entity_spawn(server);
            entity->radius = 12;
            entity->player = player->playerIndex;
            player->entityIndex = entity->entityIndex;
            return player;
        }
    }
    log_puts(LOG_ERR, "Ran out of players!");
    exit(EXIT_FAILURE);
}

void game_player_remove(Game *server, int playerIndex)
{
    Game_Player *player = &server->players[playerIndex];
    Game_Entity *entity = &server->entities[player->entityIndex];
    player->inUse = 0;
    entity->inUse = 0;
}

void game_collide_reponse_entity_entity(Game *server, float ticks, Game_Entity *e1, Game_Entity *e2)
{
    _game_collide_vec_response(ticks,
                                   e1->x, e1->y, e2->x, e2->y,
                                   &e1->x, &e1->y, &e2->x, &e2->y);
}

void game_collide_reponse_entity_tilemap(Game *server, float ticks, Game_Entity *e, int xtile, int ytile)
{
    _Rect r = _game_rect_from_tilemap(server->tilemap, xtile, ytile);
    _game_collide_tilemap_response(r, e);
}
